Train models
Having explored the retail data, let’s fit some models to it. We’ll use the first 25.5 years of data for model training, and the remaining 10 years for training.
drift is a simple random walk model incorporating a drift term.
sdrift is the seasonal counterpart to drift, ie the random walk is by season.
ar is an ARIMA model with all seasonal and nonseasonal terms chosen from the data.
ets_auto is an ETS model with the form of the components chosen from the data (either additive or multiplicative).
ets_fixed is an ETS model where the components are all additive, based on examining the plots in the previous notebook.
In addition, a nice feature of the model function is that it can fit models in parallel by leveraging the future and future.apply packages. Here, we use the multisession plan to create a background cluster of R processes for this purpose.
library(dplyr)
library(tsibbledata)
library(tsibble)
library(feasts)
library(fable)
library(future)
plan(multisession)
aus_retail_tr <- aus_retail %>%
filter(Month <= yearmonth("2008 Dec"))
aus_retail_vl <- aus_retail %>%
filter(Month > yearmonth("2008 Dec"))
mods <- model(aus_retail_tr,
drift=NAIVE(log(Turnover) ~ drift()),
sdrift=SNAIVE(log(Turnover) ~ drift()),
ar=ARIMA(log(Turnover)),
ets_auto=ETS(log(Turnover)),
ets_fixed=ETS(log(Turnover) ~ error("A") + trend("A") + season("A")),
.safely=FALSE
)
Warning in sqrt(diag(best$var.coef)): NaNs produced
Warning in sqrt(diag(best$var.coef)): NaNs produced
nrow(mods)
[1] 150
Note that there are 150 separate models for each of the above, corresponding to all observed combinations of state/territory and industry (not every industry is represented in each state). The ability to parallelise model training is thus very useful.
Let’s examine the resulting output, for one time series. The plotted output from autoplot includes the point forecasts along with the 80% and 95% prediction intervals, for each model. The actual turnover in this period is given by the black line.
library(ggplot2)
fcasts <- forecast(mods, new_data=aus_retail_vl)
fcasts %>%
filter(Industry == "Food retailing", State == "New South Wales") %>%
autoplot(data=aus_retail_vl) +
theme(legend.position="bottom")

The main feature of this plot is that the drift model is almost comically bad. Not only does it fails to capture the seasonal pattern in the data, but it also severely overestimates the growth in turnover in the validation period.
We can redo the plot, but omitting this one model and using only the 80% prediction intervals:
fcasts %>%
filter(Industry == "Food retailing", State == "New South Wales", .model != "drift") %>%
autoplot(data=aus_retail_vl, level=80) +
theme(legend.position="bottom")

This plot shows that, in fact, all of the models are systematically overestimating the growth in turnover (although the actual growth is still mostly within the prediction intervals). To see whether this is limited to this particular time series, we can also aggregate up the forecasts to the state level and plot them. There are a couple of warts to be aware of:
- Some time series actually end before the validation period, so we need to exclude them from the aggregation to avoid distorting the results.
- Currently (as of May 2020) there are still some glitches when row-binding tsibbles, so we explicitly coerce the result of
bind_rows() to a tsibble.
state_vl <- aus_retail_vl %>%
group_by(State) %>%
summarise(Turnover=sum(Turnover))
fcasts_state <- fcasts %>%
filter(Month > yearmonth("2008 Dec"), .model != "drift") %>%
group_by(State, .model) %>%
summarise(Turnover=sum(Turnover)) %>%
bind_rows(state_vl) %>%
tsibble(key=c(State, .model), index=Month) %>%
mutate(.model=ifelse(is.na(.model), ".response", .model))
fcasts_state_plot <- function(state)
{
fcasts_state %>%
filter(State == state) %>%
select(-State) %>%
autoplot(Turnover) +
theme(legend.position="bottom") +
scale_y_log10() +
annotation_logticks() +
ggtitle(state)
}
fcasts_state_plot("New South Wales")

fcasts_state_plot("Victoria")

fcasts_state_plot("Queensland")

fcasts_state_plot("South Australia")

fcasts_state_plot("Western Australia")

fcasts_state_plot("Tasmania")

fcasts_state_plot("Northern Territory")

fcasts_state_plot("Australian Capital Territory")

This shows that all of the forecasts are systematically overestimating the trend. What’s causing this? The reason is probably because of how we split the data into training and validation periods. The training data terminates at the end of 2008, which corresponds to the global financial crisis; conversely, the validation data starts at a point in which the economy is low and beginning to recover from the crisis.
Update models to 2013
To test this hypothesis, let’s refit the models, but this time with the training period extended to the end of 2013. The drift model is omitted as it is clearly inappropriate for the data.
aus_retail_2013_tr <- aus_retail %>%
filter(Month <= yearmonth("2013 Dec"))
aus_retail_2013_vl <- aus_retail %>%
filter(Month > yearmonth("2013 Dec"))
mods_2013 <- model(aus_retail_2013_tr,
sdrift=SNAIVE(log(Turnover) ~ drift()),
ar=ARIMA(log(Turnover)),
ets_auto=ETS(log(Turnover)),
ets_fixed=ETS(log(Turnover) ~ error("A") + trend("A") + season("A")),
.safely=FALSE
)
Warning in sqrt(diag(best$var.coef)): NaNs produced
Warning in sqrt(diag(best$var.coef)): NaNs produced
Warning in sqrt(diag(best$var.coef)): NaNs produced
Warning in sqrt(diag(best$var.coef)): NaNs produced
Warning in sqrt(diag(best$var.coef)): NaNs produced
fcasts_2013 <- forecast(mods_2013, new_data=aus_retail_2013_vl)
fcasts_state_2013 <- fcasts_2013 %>%
filter(Month > yearmonth("2013 Dec")) %>%
group_by(State, .model) %>%
summarise(Turnover=sum(Turnover)) %>%
bind_rows(state_vl) %>%
tsibble(key=c(State, .model), index=Month) %>%
mutate(.model=ifelse(is.na(.model), ".response", .model))
fcasts_state_2013_plot <- function(state)
{
fcasts_state_2013 %>%
filter(State == state) %>%
select(-State) %>%
autoplot(Turnover) +
theme(legend.position="bottom") +
scale_y_log10() +
annotation_logticks() +
ggtitle(state)
}
fcasts_state_2013_plot("New South Wales")

fcasts_state_2013_plot("Victoria")

fcasts_state_2013_plot("Queensland")

fcasts_state_2013_plot("South Australia")

fcasts_state_2013_plot("Western Australia")

fcasts_state_2013_plot("Tasmania")

fcasts_state_2013_plot("Northern Territory")

fcasts_state_2013_plot("Australian Capital Territory")

The plots show much better agreement between forecasts and actuals, especially for the larger state (NSW and Victoria). Nevertheless, there is still substantial error for the smaller states. This is probably because these states were hit harder by the global recession and took longer to recover.
Measuring accuracy
A variety of point estimate accuracy measures are provided in the fabletools package. For this dataset, the MAPE (mean absolute percentage error) is appropriate. In general, you should not put too much emphasis on such measures as they play down the uncertainty inherent in any statistical inference task, let alone forecasting; remember to look at the prediction intervals as well to guide you on whether a model is adequate. Also, it’s better to treat these as relative measures, to help us decide which of a number of competing models to use, rather than looking at the absolute accuracy.
Nevertheless, let’s examine the MAPE scores of the different model types, both by state, and overall.
library(tidyr)
fcasts_2013_wide <- fcasts_2013 %>%
filter(Month > yearmonth("2013 Dec")) %>%
as_tibble() %>%
pivot_wider(id_cols=c(State, Industry, .model, Month), names_from=.model, values_from=Turnover) %>%
inner_join(aus_retail_2013_vl, by=c("State", "Industry", "Month"))
fcasts_2013_wide %>%
group_by(State) %>%
group_modify(function(.x, .y)
summarise_at(.x, vars(sdrift:ets_fixed), function(x) MAPE(x - .x$Turnover, .x$Turnover))
)
fcasts_2013_wide %>%
summarise_at(vars(sdrift:ets_fixed), function(x) MAPE(x - .$Turnover, .$Turnover))
This broadly confirms the patterns seen in the plots above. The sdrift model performs worst, which is unsurprising given that it is simplistic by design. The ets_auto model performs best, probably because this particular dataset exhibits very clear trends and seasonal patterns. The forecast accuracy is best for the bigger states (NSW and Victoria) and worst for the Northern Territory and Western Australia.
LS0tCnRpdGxlOiAiVXNpbmcgVGlkeXZlcnRzIHdpdGggdGhlIEF1c3RyYWxpYW4gcmV0YWlsIGRhdGE6IG1vZGVsbGluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgVHJhaW4gbW9kZWxzCgpIYXZpbmcgZXhwbG9yZWQgdGhlIHJldGFpbCBkYXRhLCBsZXQncyBmaXQgc29tZSBtb2RlbHMgdG8gaXQuIFdlJ2xsIHVzZSB0aGUgZmlyc3QgMjUuNSB5ZWFycyBvZiBkYXRhIGZvciBtb2RlbCB0cmFpbmluZywgYW5kIHRoZSByZW1haW5pbmcgMTAgeWVhcnMgZm9yIHRyYWluaW5nLgoKLSBgZHJpZnRgIGlzIGEgc2ltcGxlIHJhbmRvbSB3YWxrIG1vZGVsIGluY29ycG9yYXRpbmcgYSBkcmlmdCB0ZXJtLgotIGBzZHJpZnRgIGlzIHRoZSBzZWFzb25hbCBjb3VudGVycGFydCB0byBgZHJpZnRgLCBpZSB0aGUgcmFuZG9tIHdhbGsgaXMgYnkgc2Vhc29uLgotIGBhcmAgaXMgYW4gQVJJTUEgbW9kZWwgd2l0aCBhbGwgc2Vhc29uYWwgYW5kIG5vbnNlYXNvbmFsIHRlcm1zIGNob3NlbiBmcm9tIHRoZSBkYXRhLgotIGBldHNfYXV0b2AgaXMgYW4gRVRTIG1vZGVsIHdpdGggdGhlIGZvcm0gb2YgdGhlIGNvbXBvbmVudHMgY2hvc2VuIGZyb20gdGhlIGRhdGEgKGVpdGhlciBhZGRpdGl2ZSBvciBtdWx0aXBsaWNhdGl2ZSkuCi0gYGV0c19maXhlZGAgaXMgYW4gRVRTIG1vZGVsIHdoZXJlIHRoZSBjb21wb25lbnRzIGFyZSBhbGwgYWRkaXRpdmUsIGJhc2VkIG9uIGV4YW1pbmluZyB0aGUgcGxvdHMgaW4gdGhlIHByZXZpb3VzIG5vdGVib29rLgoKSW4gYWRkaXRpb24sIGEgbmljZSBmZWF0dXJlIG9mIHRoZSBgbW9kZWxgIGZ1bmN0aW9uIGlzIHRoYXQgaXQgY2FuIGZpdCBtb2RlbHMgaW4gcGFyYWxsZWwgYnkgbGV2ZXJhZ2luZyB0aGUgZnV0dXJlIGFuZCBmdXR1cmUuYXBwbHkgcGFja2FnZXMuIEhlcmUsIHdlIHVzZSB0aGUgYG11bHRpc2Vzc2lvbmAgcGxhbiB0byBjcmVhdGUgYSBiYWNrZ3JvdW5kIGNsdXN0ZXIgb2YgUiBwcm9jZXNzZXMgZm9yIHRoaXMgcHVycG9zZS4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRzaWJibGVkYXRhKQpsaWJyYXJ5KHRzaWJibGUpCmxpYnJhcnkoZmVhc3RzKQpsaWJyYXJ5KGZhYmxlKQpsaWJyYXJ5KGZ1dHVyZSkKCnBsYW4obXVsdGlzZXNzaW9uKQoKYXVzX3JldGFpbF90ciA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoIDw9IHllYXJtb250aCgiMjAwOCBEZWMiKSkKYXVzX3JldGFpbF92bCA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDA4IERlYyIpKQoKbW9kcyA8LSBtb2RlbChhdXNfcmV0YWlsX3RyLAogICAgZHJpZnQ9TkFJVkUobG9nKFR1cm5vdmVyKSB+IGRyaWZ0KCkpLAogICAgc2RyaWZ0PVNOQUlWRShsb2coVHVybm92ZXIpIH4gZHJpZnQoKSksCiAgICBhcj1BUklNQShsb2coVHVybm92ZXIpKSwKICAgIGV0c19hdXRvPUVUUyhsb2coVHVybm92ZXIpKSwKICAgIGV0c19maXhlZD1FVFMobG9nKFR1cm5vdmVyKSB+IGVycm9yKCJBIikgKyB0cmVuZCgiQSIpICsgc2Vhc29uKCJBIikpLAogICAgLnNhZmVseT1GQUxTRQopCgpucm93KG1vZHMpCmBgYAoKTm90ZSB0aGF0IHRoZXJlIGFyZSAxNTAgc2VwYXJhdGUgbW9kZWxzIGZvciBlYWNoIG9mIHRoZSBhYm92ZSwgY29ycmVzcG9uZGluZyB0byBhbGwgb2JzZXJ2ZWQgY29tYmluYXRpb25zIG9mIHN0YXRlL3RlcnJpdG9yeSBhbmQgaW5kdXN0cnkgKG5vdCBldmVyeSBpbmR1c3RyeSBpcyByZXByZXNlbnRlZCBpbiBlYWNoIHN0YXRlKS4gVGhlIGFiaWxpdHkgdG8gcGFyYWxsZWxpc2UgbW9kZWwgdHJhaW5pbmcgaXMgdGh1cyB2ZXJ5IHVzZWZ1bC4KCkxldCdzIGV4YW1pbmUgdGhlIHJlc3VsdGluZyBvdXRwdXQsIGZvciBvbmUgdGltZSBzZXJpZXMuIFRoZSBwbG90dGVkIG91dHB1dCBmcm9tIGBhdXRvcGxvdGAgaW5jbHVkZXMgdGhlIHBvaW50IGZvcmVjYXN0cyBhbG9uZyB3aXRoIHRoZSA4MCUgYW5kIDk1JSBwcmVkaWN0aW9uIGludGVydmFscywgZm9yIGVhY2ggbW9kZWwuIFRoZSBhY3R1YWwgdHVybm92ZXIgaW4gdGhpcyBwZXJpb2QgaXMgZ2l2ZW4gYnkgdGhlIGJsYWNrIGxpbmUuCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQoKZmNhc3RzIDwtIGZvcmVjYXN0KG1vZHMsIG5ld19kYXRhPWF1c19yZXRhaWxfdmwpCgpmY2FzdHMgJT4lCiAgICBmaWx0ZXIoSW5kdXN0cnkgPT0gIkZvb2QgcmV0YWlsaW5nIiwgU3RhdGUgPT0gIk5ldyBTb3V0aCBXYWxlcyIpICU+JQogICAgYXV0b3Bsb3QoZGF0YT1hdXNfcmV0YWlsX3ZsKSArCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQpgYGAKClRoZSBtYWluIGZlYXR1cmUgb2YgdGhpcyBwbG90IGlzIHRoYXQgdGhlIGBkcmlmdGAgbW9kZWwgaXMgYWxtb3N0IGNvbWljYWxseSBiYWQuIE5vdCBvbmx5IGRvZXMgaXQgZmFpbHMgdG8gY2FwdHVyZSB0aGUgc2Vhc29uYWwgcGF0dGVybiBpbiB0aGUgZGF0YSwgYnV0IGl0IGFsc28gc2V2ZXJlbHkgb3ZlcmVzdGltYXRlcyB0aGUgZ3Jvd3RoIGluIHR1cm5vdmVyIGluIHRoZSB2YWxpZGF0aW9uIHBlcmlvZC4KCldlIGNhbiByZWRvIHRoZSBwbG90LCBidXQgb21pdHRpbmcgdGhpcyBvbmUgbW9kZWwgYW5kIHVzaW5nIG9ubHkgdGhlIDgwJSBwcmVkaWN0aW9uIGludGVydmFsczoKCmBgYHtyfQpmY2FzdHMgJT4lCiAgICBmaWx0ZXIoSW5kdXN0cnkgPT0gIkZvb2QgcmV0YWlsaW5nIiwgU3RhdGUgPT0gIk5ldyBTb3V0aCBXYWxlcyIsIC5tb2RlbCAhPSAiZHJpZnQiKSAlPiUKICAgIGF1dG9wbG90KGRhdGE9YXVzX3JldGFpbF92bCwgbGV2ZWw9ODApICsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpCmBgYAoKVGhpcyBwbG90IHNob3dzIHRoYXQsIGluIGZhY3QsIF9hbGxfIG9mIHRoZSBtb2RlbHMgYXJlIHN5c3RlbWF0aWNhbGx5IG92ZXJlc3RpbWF0aW5nIHRoZSBncm93dGggaW4gdHVybm92ZXIgKGFsdGhvdWdoIHRoZSBhY3R1YWwgZ3Jvd3RoIGlzIHN0aWxsIG1vc3RseSB3aXRoaW4gdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWxzKS4gVG8gc2VlIHdoZXRoZXIgdGhpcyBpcyBsaW1pdGVkIHRvIHRoaXMgcGFydGljdWxhciB0aW1lIHNlcmllcywgd2UgY2FuIGFsc28gYWdncmVnYXRlIHVwIHRoZSBmb3JlY2FzdHMgdG8gdGhlIHN0YXRlIGxldmVsIGFuZCBwbG90IHRoZW0uIFRoZXJlIGFyZSBhIGNvdXBsZSBvZiB3YXJ0cyB0byBiZSBhd2FyZSBvZjoKCi0gU29tZSB0aW1lIHNlcmllcyBhY3R1YWxseSBlbmQgYmVmb3JlIHRoZSB2YWxpZGF0aW9uIHBlcmlvZCwgc28gd2UgbmVlZCB0byBleGNsdWRlIHRoZW0gZnJvbSB0aGUgYWdncmVnYXRpb24gdG8gYXZvaWQgZGlzdG9ydGluZyB0aGUgcmVzdWx0cy4KLSBDdXJyZW50bHkgKGFzIG9mIE1heSAyMDIwKSB0aGVyZSBhcmUgc3RpbGwgc29tZSBnbGl0Y2hlcyB3aGVuIHJvdy1iaW5kaW5nIHRzaWJibGVzLCBzbyB3ZSBleHBsaWNpdGx5IGNvZXJjZSB0aGUgcmVzdWx0IG9mIGBiaW5kX3Jvd3MoKWAgdG8gYSB0c2liYmxlLgoKYGBge3J9CnN0YXRlX3ZsIDwtIGF1c19yZXRhaWxfdmwgJT4lCiAgICBncm91cF9ieShTdGF0ZSkgJT4lCiAgICBzdW1tYXJpc2UoVHVybm92ZXI9c3VtKFR1cm5vdmVyKSkKCmZjYXN0c19zdGF0ZSA8LSBmY2FzdHMgJT4lCiAgICBmaWx0ZXIoTW9udGggPiB5ZWFybW9udGgoIjIwMDggRGVjIiksIC5tb2RlbCAhPSAiZHJpZnQiKSAlPiUKICAgIGdyb3VwX2J5KFN0YXRlLCAubW9kZWwpICU+JQogICAgc3VtbWFyaXNlKFR1cm5vdmVyPXN1bShUdXJub3ZlcikpICU+JQogICAgYmluZF9yb3dzKHN0YXRlX3ZsKSAlPiUKICAgIHRzaWJibGUoa2V5PWMoU3RhdGUsIC5tb2RlbCksIGluZGV4PU1vbnRoKSAlPiUKICAgIG11dGF0ZSgubW9kZWw9aWZlbHNlKGlzLm5hKC5tb2RlbCksICIucmVzcG9uc2UiLCAubW9kZWwpKQoKZmNhc3RzX3N0YXRlX3Bsb3QgPC0gZnVuY3Rpb24oc3RhdGUpCnsKICAgIGZjYXN0c19zdGF0ZSAlPiUKICAgICAgICBmaWx0ZXIoU3RhdGUgPT0gc3RhdGUpICU+JQogICAgICAgIHNlbGVjdCgtU3RhdGUpICU+JQogICAgICAgIGF1dG9wbG90KFR1cm5vdmVyKSArCiAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgKwogICAgICAgICAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgICAgICAgICBhbm5vdGF0aW9uX2xvZ3RpY2tzKCkgKwogICAgICAgICAgICBnZ3RpdGxlKHN0YXRlKQp9CgpmY2FzdHNfc3RhdGVfcGxvdCgiTmV3IFNvdXRoIFdhbGVzIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlZpY3RvcmlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlF1ZWVuc2xhbmQiKQpmY2FzdHNfc3RhdGVfcGxvdCgiU291dGggQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIldlc3Rlcm4gQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIlRhc21hbmlhIikKZmNhc3RzX3N0YXRlX3Bsb3QoIk5vcnRoZXJuIFRlcnJpdG9yeSIpCmZjYXN0c19zdGF0ZV9wbG90KCJBdXN0cmFsaWFuIENhcGl0YWwgVGVycml0b3J5IikKYGBgCgpUaGlzIHNob3dzIHRoYXQgYWxsIG9mIHRoZSBmb3JlY2FzdHMgYXJlIHN5c3RlbWF0aWNhbGx5IG92ZXJlc3RpbWF0aW5nIHRoZSB0cmVuZC4gV2hhdCdzIGNhdXNpbmcgdGhpcz8gVGhlIHJlYXNvbiBpcyBwcm9iYWJseSBiZWNhdXNlIG9mIGhvdyB3ZSBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHBlcmlvZHMuIFRoZSB0cmFpbmluZyBkYXRhIHRlcm1pbmF0ZXMgYXQgdGhlIGVuZCBvZiAyMDA4LCB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgZ2xvYmFsIGZpbmFuY2lhbCBjcmlzaXM7IGNvbnZlcnNlbHksIHRoZSB2YWxpZGF0aW9uIGRhdGEgc3RhcnRzIGF0IGEgcG9pbnQgaW4gd2hpY2ggdGhlIGVjb25vbXkgaXMgbG93IGFuZCBiZWdpbm5pbmcgdG8gcmVjb3ZlciBmcm9tIHRoZSBjcmlzaXMuCgojIyBVcGRhdGUgbW9kZWxzIHRvIDIwMTMKClRvIHRlc3QgdGhpcyBoeXBvdGhlc2lzLCBsZXQncyByZWZpdCB0aGUgbW9kZWxzLCBidXQgdGhpcyB0aW1lIHdpdGggdGhlIHRyYWluaW5nIHBlcmlvZCBleHRlbmRlZCB0byB0aGUgZW5kIG9mIDIwMTMuIFRoZSBgZHJpZnRgIG1vZGVsIGlzIG9taXR0ZWQgYXMgaXQgaXMgY2xlYXJseSBpbmFwcHJvcHJpYXRlIGZvciB0aGUgZGF0YS4KCmBgYHtyfQphdXNfcmV0YWlsXzIwMTNfdHIgPC0gYXVzX3JldGFpbCAlPiUKICAgIGZpbHRlcihNb250aCA8PSB5ZWFybW9udGgoIjIwMTMgRGVjIikpCmF1c19yZXRhaWxfMjAxM192bCA8LSBhdXNfcmV0YWlsICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDEzIERlYyIpKQoKbW9kc18yMDEzIDwtIG1vZGVsKGF1c19yZXRhaWxfMjAxM190ciwKICAgIHNkcmlmdD1TTkFJVkUobG9nKFR1cm5vdmVyKSB+IGRyaWZ0KCkpLAogICAgYXI9QVJJTUEobG9nKFR1cm5vdmVyKSksCiAgICBldHNfYXV0bz1FVFMobG9nKFR1cm5vdmVyKSksCiAgICBldHNfZml4ZWQ9RVRTKGxvZyhUdXJub3ZlcikgfiBlcnJvcigiQSIpICsgdHJlbmQoIkEiKSArIHNlYXNvbigiQSIpKSwKICAgIC5zYWZlbHk9RkFMU0UKKQoKZmNhc3RzXzIwMTMgPC0gZm9yZWNhc3QobW9kc18yMDEzLCBuZXdfZGF0YT1hdXNfcmV0YWlsXzIwMTNfdmwpCgpmY2FzdHNfc3RhdGVfMjAxMyA8LSBmY2FzdHNfMjAxMyAlPiUKICAgIGZpbHRlcihNb250aCA+IHllYXJtb250aCgiMjAxMyBEZWMiKSkgJT4lCiAgICBncm91cF9ieShTdGF0ZSwgLm1vZGVsKSAlPiUKICAgIHN1bW1hcmlzZShUdXJub3Zlcj1zdW0oVHVybm92ZXIpKSAlPiUKICAgIGJpbmRfcm93cyhzdGF0ZV92bCkgJT4lCiAgICB0c2liYmxlKGtleT1jKFN0YXRlLCAubW9kZWwpLCBpbmRleD1Nb250aCkgJT4lCiAgICBtdXRhdGUoLm1vZGVsPWlmZWxzZShpcy5uYSgubW9kZWwpLCAiLnJlc3BvbnNlIiwgLm1vZGVsKSkKCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QgPC0gZnVuY3Rpb24oc3RhdGUpCnsKICAgIGZjYXN0c19zdGF0ZV8yMDEzICU+JQogICAgICAgIGZpbHRlcihTdGF0ZSA9PSBzdGF0ZSkgJT4lCiAgICAgICAgc2VsZWN0KC1TdGF0ZSkgJT4lCiAgICAgICAgYXV0b3Bsb3QoVHVybm92ZXIpICsKICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgICAgICAgICAgIHNjYWxlX3lfbG9nMTAoKSArCiAgICAgICAgICAgIGFubm90YXRpb25fbG9ndGlja3MoKSArCiAgICAgICAgICAgIGdndGl0bGUoc3RhdGUpCn0KCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIk5ldyBTb3V0aCBXYWxlcyIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIlZpY3RvcmlhIikKZmNhc3RzX3N0YXRlXzIwMTNfcGxvdCgiUXVlZW5zbGFuZCIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIlNvdXRoIEF1c3RyYWxpYSIpCmZjYXN0c19zdGF0ZV8yMDEzX3Bsb3QoIldlc3Rlcm4gQXVzdHJhbGlhIikKZmNhc3RzX3N0YXRlXzIwMTNfcGxvdCgiVGFzbWFuaWEiKQpmY2FzdHNfc3RhdGVfMjAxM19wbG90KCJOb3J0aGVybiBUZXJyaXRvcnkiKQpmY2FzdHNfc3RhdGVfMjAxM19wbG90KCJBdXN0cmFsaWFuIENhcGl0YWwgVGVycml0b3J5IikKYGBgCgpUaGUgcGxvdHMgc2hvdyBtdWNoIGJldHRlciBhZ3JlZW1lbnQgYmV0d2VlbiBmb3JlY2FzdHMgYW5kIGFjdHVhbHMsIGVzcGVjaWFsbHkgZm9yIHRoZSBsYXJnZXIgc3RhdGUgKE5TVyBhbmQgVmljdG9yaWEpLiBOZXZlcnRoZWxlc3MsIHRoZXJlIGlzIHN0aWxsIHN1YnN0YW50aWFsIGVycm9yIGZvciB0aGUgc21hbGxlciBzdGF0ZXMuIFRoaXMgaXMgcHJvYmFibHkgYmVjYXVzZSB0aGVzZSBzdGF0ZXMgd2VyZSBoaXQgaGFyZGVyIGJ5IHRoZSBnbG9iYWwgcmVjZXNzaW9uIGFuZCB0b29rIGxvbmdlciB0byByZWNvdmVyLgoKCiMjIE1lYXN1cmluZyBhY2N1cmFjeQoKQSB2YXJpZXR5IG9mIHBvaW50IGVzdGltYXRlIGFjY3VyYWN5IG1lYXN1cmVzIGFyZSBwcm92aWRlZCBpbiB0aGUgZmFibGV0b29scyBwYWNrYWdlLiBGb3IgdGhpcyBkYXRhc2V0LCB0aGUgTUFQRSAobWVhbiBhYnNvbHV0ZSBwZXJjZW50YWdlIGVycm9yKSBpcyBhcHByb3ByaWF0ZS4gSW4gZ2VuZXJhbCwgeW91IHNob3VsZCBub3QgcHV0IHRvbyBtdWNoIGVtcGhhc2lzIG9uIHN1Y2ggbWVhc3VyZXMgYXMgdGhleSBwbGF5IGRvd24gdGhlIHVuY2VydGFpbnR5IGluaGVyZW50IGluIGFueSBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgdGFzaywgbGV0IGFsb25lIGZvcmVjYXN0aW5nOyByZW1lbWJlciB0byBsb29rIGF0IHRoZSBwcmVkaWN0aW9uIGludGVydmFscyBhcyB3ZWxsIHRvIGd1aWRlIHlvdSBvbiB3aGV0aGVyIGEgbW9kZWwgaXMgYWRlcXVhdGUuIEFsc28sIGl0J3MgYmV0dGVyIHRvIHRyZWF0IHRoZXNlIGFzIF9yZWxhdGl2ZV8gbWVhc3VyZXMsIHRvIGhlbHAgdXMgZGVjaWRlIHdoaWNoIG9mIGEgbnVtYmVyIG9mIGNvbXBldGluZyBtb2RlbHMgdG8gdXNlLCByYXRoZXIgdGhhbiBsb29raW5nIGF0IHRoZSBhYnNvbHV0ZSBhY2N1cmFjeS4KCiBOZXZlcnRoZWxlc3MsIGxldCdzIGV4YW1pbmUgdGhlIE1BUEUgc2NvcmVzIG9mIHRoZSBkaWZmZXJlbnQgbW9kZWwgdHlwZXMsIGJvdGggYnkgc3RhdGUsIGFuZCBvdmVyYWxsLgoKYGBge3J9CmxpYnJhcnkodGlkeXIpCgpmY2FzdHNfMjAxM193aWRlIDwtIGZjYXN0c18yMDEzICU+JQogICAgZmlsdGVyKE1vbnRoID4geWVhcm1vbnRoKCIyMDEzIERlYyIpKSAlPiUKICAgIGFzX3RpYmJsZSgpICU+JQogICAgcGl2b3Rfd2lkZXIoaWRfY29scz1jKFN0YXRlLCBJbmR1c3RyeSwgLm1vZGVsLCBNb250aCksIG5hbWVzX2Zyb209Lm1vZGVsLCB2YWx1ZXNfZnJvbT1UdXJub3ZlcikgJT4lCiAgICBpbm5lcl9qb2luKGF1c19yZXRhaWxfMjAxM192bCwgYnk9YygiU3RhdGUiLCAiSW5kdXN0cnkiLCAiTW9udGgiKSkKCmZjYXN0c18yMDEzX3dpZGUgJT4lCiAgICBncm91cF9ieShTdGF0ZSkgJT4lCiAgICBncm91cF9tb2RpZnkoZnVuY3Rpb24oLngsIC55KQogICAgICAgIHN1bW1hcmlzZV9hdCgueCwgdmFycyhzZHJpZnQ6ZXRzX2ZpeGVkKSwgZnVuY3Rpb24oeCkgTUFQRSh4IC0gLngkVHVybm92ZXIsIC54JFR1cm5vdmVyKSkKICAgICkKCmZjYXN0c18yMDEzX3dpZGUgJT4lCiAgICBzdW1tYXJpc2VfYXQodmFycyhzZHJpZnQ6ZXRzX2ZpeGVkKSwgZnVuY3Rpb24oeCkgTUFQRSh4IC0gLiRUdXJub3ZlciwgLiRUdXJub3ZlcikpCmBgYAoKVGhpcyBicm9hZGx5IGNvbmZpcm1zIHRoZSBwYXR0ZXJucyBzZWVuIGluIHRoZSBwbG90cyBhYm92ZS4gVGhlIGBzZHJpZnRgIG1vZGVsIHBlcmZvcm1zIHdvcnN0LCB3aGljaCBpcyB1bnN1cnByaXNpbmcgZ2l2ZW4gdGhhdCBpdCBpcyBzaW1wbGlzdGljIGJ5IGRlc2lnbi4gVGhlIGBldHNfYXV0b2AgbW9kZWwgcGVyZm9ybXMgYmVzdCwgcHJvYmFibHkgYmVjYXVzZSB0aGlzIHBhcnRpY3VsYXIgZGF0YXNldCBleGhpYml0cyB2ZXJ5IGNsZWFyIHRyZW5kcyBhbmQgc2Vhc29uYWwgcGF0dGVybnMuIFRoZSBmb3JlY2FzdCBhY2N1cmFjeSBpcyBiZXN0IGZvciB0aGUgYmlnZ2VyIHN0YXRlcyAoTlNXIGFuZCBWaWN0b3JpYSkgYW5kIHdvcnN0IGZvciB0aGUgTm9ydGhlcm4gVGVycml0b3J5IGFuZCBXZXN0ZXJuIEF1c3RyYWxpYS4KCiMjIyBDb21tZW50CgpUaGVyZSBpcyBhIHBhcnRpY3VsYXJseSB0aW1lbHkgYW5kIGltcG9ydGFudCBvYnNlcnZhdGlvbiB0byBtYWtlIHJlZ2FyZGluZyB0aGlzIGRhdGFzZXQuIEZyb20gYWJvdmUsIHdlIHNhdyB0aGF0IHVwZGF0aW5nIHRoZSBtb2RlbHMgdG8gdXNlIHRoZSBkYXRhIHVwIHRvIDIwMTMgZ2F2ZSBiZXR0ZXIgZm9yZWNhc3QgYWNjdXJhY3ksIGVzcGVjaWFsbHkgZm9yIE5TVyBhbmQgVmljdG9yaWEuIEFzc3VtaW5nIHRoYXQgd2Ugd2VyZSBvbmx5IGludGVyZXN0ZWQgaW4gdGhlc2UgdHdvIHN0YXRlcywgd2hhdCB3b3VsZCBoYXBwZW4gaWYgd2Ugd2VyZSB0byB1c2UgdGhlIG1vZGVscyB0byBvYnRhaW4gZm9yZWNhc3RzIGZvciAyMDIwIGFuZCBiZXlvbmQ/IERlc3BpdGUgdGhlIGdvb2QgcmVzdWx0cyBvbiBwYXN0IGRhdGEsIHRoZXkgd291bGQgYWxtb3N0IGNlcnRhaW5seSBiZSB2ZXJ5IHdpZGUgb2YgdGhlIG1hcmsuIFRoaXMgaXMgYmVjYXVzZSBldmVuIHRoZSBiZXN0IG1vZGVsIGNvdWxkIG5vdCBwb3NzaWJseSBhbnRpY2lwYXRlIHRoZSBtYXNzaXZlIGdsb2JhbCByZWNlc3Npb24gY2F1c2VkIGJ5IHRoZSBDT1ZJRC0xOSBwYW5kZW1pYy4gKE9mIGNvdXJzZSwgdGhlIHNhbWUgd291bGQgYXBwbHkgZm9yIGFueSBzdWJqZWN0aXZlIGZvcmVjYXN0IGJhc2VkIG9uIGV4cGVydCBrbm93bGVkZ2UsIHNvIHRoaXMgaXMgbm90IGFuIGVuZG9yc2VtZW50IG9mIGp1ZGdlbWVudGFsIGZvcmVjYXN0aW5nLikKClRoZXNlIHJlc3VsdHMgZGVtb25zdHJhdGUgdGhlIHJpc2tzIGluaGVyZW50IGluIGZvcmVjYXN0aW5nLCBlc3BlY2lhbGx5IHdoZW4gdGhlcmUgaXMgYSBzdHJvbmcgdHJlbmQuIEV2ZW4gYSB0cmVuZCB0aGF0IGFwcGVhcnMgdG8gYmUgc3RhYmxlIG92ZXIgdGltZSBjYW4gZGl2ZXJnZSBmb3IgcmVhc29ucyBub3QgY2FwdHVyZWQgaW4gdGhlIGRhdGEsIHJlc3VsdGluZyBpbiBzeXN0ZW1hdGljIGZvcmVjYXN0IGVycm9ycy4gQW55IGFzc2Vzc21lbnQgb2YgbW9kZWwgcGVyZm9ybWFuY2Ugc2hvdWxkIGJlIGludGVycHJldGVkIGluIGNvbnRleHQsIHdpdGggdGhlIHBvc3NpYmlsaXR5IG9mIGV4dGVybmFsIHNoaWZ0cyB0YWtlbiBpbnRvIGFjY291bnQuCgo=